
using System.Collections.Generic;
using System;
using Gltf;
using Unity.Mathematics;
using UnityEngine;
using Newtonsoft.Json.Linq;
using Geometry;
using Unity.Collections;
using System.Runtime.InteropServices;
using Mikktspace.NET;
using UnityEngine.Rendering;
using Cesium3DTilesSelection;

struct CreateModelOptions
{
    public bool alwaysIncludeTangents;
    // #if PHYSICS_INTERFACE_PHYSX
    //   IPhysXCooking* pPhysXCooking ;
    // #endif
};

public class LoadModelResult
{
    public MetadataPrimitive Metadata;
    public UnityEngine.Mesh RenderData;
    public Model pModel;
    public MeshPrimitive pMeshPrimitive;
    public Gltf.Material pMaterial;
    public double4x4 transform;
    public string name;

    public Texture2D baseColorTexture;
    public Texture2D metallicRoughnessTexture;
    public Texture2D normalTexture;
    public Texture2D emissiveTexture;
    public Texture2D occlusionTexture;
    public Texture2D waterMaskTexture;
    public Dictionary<string, int> textureCoordinateParameters;

    public bool onlyLand;
    public bool onlyWater;

    public double waterMaskTranslationX;
    public double waterMaskTranslationY;
    public double waterMaskScale;

    const int maximumOverlayTextureCoordinateIDs = 2;
    public int[] overlayTextureCoordinateIDToUVIndex;

    public LoadModelResult(string _name)
    {
        Metadata = null;
        RenderData = null;
        pModel = null;
        pMeshPrimitive = null;
        pMaterial = null;
        transform = double4x4.identity;
        name = _name;
        baseColorTexture = null;
        metallicRoughnessTexture = null;
        normalTexture = null;
        emissiveTexture = null;
        occlusionTexture = null;
        waterMaskTexture = null;
        textureCoordinateParameters = null;
        onlyLand = false;
        onlyWater = true;
        waterMaskTranslationX = 0;
        waterMaskTranslationY = 0;
        waterMaskScale = 0;
        overlayTextureCoordinateIDToUVIndex = new int[maximumOverlayTextureCoordinateIDs];
    }
};

public class CesiumGltfComponent : MonoBehaviour
{
    private static Gltf.Material defaultMaterial;
    private static Gltf.MaterialPBRMetallicRoughness defaultPbrMetallicRoughness;

    public UnityEngine.Material BaseMaterialWithWater { get; private set; }
    public UnityEngine.Material BaseMaterial { get; private set; }
    public UnityEngine.Texture Transparent1x1 { get; private set; }

    internal void UpdateTransformFromCesium(double4x4 cesiumToUnreal)
    {
        throw new NotImplementedException();
    }

    internal static object CreateOffGameThread(Model model, double4x4 transform, CreateModelOptions options)
    {
        return loadModelAnyThreadPart(model, transform, options);
    }

    static List<LoadModelResult> loadModelAnyThreadPart(Model model, double4x4 transform, CreateModelOptions options)
    {

        List<LoadModelResult> result = new List<LoadModelResult>();

        double4x4 rootTransform = transform;

        {
            Debug.Log("Apply transforms");
            applyRtcCenter(model, ref rootTransform);
            applyGltfUpAxisTransform(model, ref rootTransform);
        }

        if (model.scene >= 0 && model.scene < model.scenes.Count)
        {
            // Show the default scene
            Scene defaultScene = model.scenes[model.scene];
            foreach (int nodeId in defaultScene.nodes)
            {
                loadNode(result, model, model.nodes[nodeId], rootTransform, options);
            }
        }
        else if (model.scenes.Count > 0)
        {
            // There's no default, so show the first scene
            Scene defaultScene = model.scenes[0];
            foreach (int nodeId in defaultScene.nodes)
            {
                loadNode(result, model, model.nodes[nodeId], rootTransform, options);
            }
        }
        else if (model.nodes.Count > 0)
        {
            // No scenes at all, use the first node as the root node.
            loadNode(result, model, model.nodes[0], rootTransform, options);
        }
        else if (model.meshes.Count > 0)
        {
            // No nodes either, show all the meshes.
            foreach (Gltf.Mesh mesh in model.meshes)
            {
                loadMesh(result, model, mesh, rootTransform, options);
            }
        }

        return result;
    }



    internal static object CreateOnGameThread(Cesium3DTileset self, object pLoadThreadResult, double4x4 cesiumToUnrealTransform, UnityEngine.Material pBaseMaterial, UnityEngine.Material pBaseWaterMaterial)
    {
        List<LoadModelResult> result = pLoadThreadResult as List<LoadModelResult>;
        if (result.Count == 0)
        {
            return null;
        }

        GameObject pgo = new GameObject();
        pgo.transform.position = Vector3.zero;
        pgo.transform.localScale = Vector3.one;
        pgo.transform.rotation = quaternion.identity;

        var gltf = pgo.AddComponent<CesiumGltfComponent>();
        if (pBaseMaterial)
        {
            gltf.BaseMaterial = pBaseMaterial;
        }

        if (pBaseWaterMaterial)
        {
            gltf.BaseMaterialWithWater = pBaseWaterMaterial;
        }

        foreach (LoadModelResult model in result)
        {
            loadModelGameThreadPart(gltf, model, cesiumToUnrealTransform);
        }
        // Gltf.SetVisibility(false, true);
        // Gltf.SetCollisionEnabled(ECollisionEnabled::NoCollision);
        return gltf;
    }
    internal void DetachRasterTile(Tile tile, RasterOverlayTile rasterTile, Texture2D texture2D)
    {
        var primitiveComponent = GetComponentsInChildren<CesiumGltfPrimitiveComponent>();

        foreach (var pPrimitive in primitiveComponent)
        {
            //if (pCesiumData)
            //{
            //FString name(
            //    UTF8_TO_TCHAR(rasterTile.getOverlay().getName().c_str()));
            //for (int32 i = 0; i < pCesiumData->LayerNames.Num(); ++i)
            //{
            //    if (pCesiumData->LayerNames[i] != name)
            //    {
            //        continue;
            //    }

            //    pMaterial->SetTextureParameterValueByInfo(
            //        FMaterialParameterInfo(
            //            "Texture",
            //            EMaterialParameterAssociation::LayerParameter,
            //            i),
            //        this->Transparent1x1);
            //}
            //}
            //else
            //{
            pPrimitive.GetComponent<Renderer>().material.SetTexture("_Texture", this.Transparent1x1);
            //}
        }
    }

    internal void AttachRasterTile(Tile tile, RasterOverlayTile rasterTile, Texture2D pTexture,
    double2 translation, double2 scale, int textureCoordinateID)
    {
        Vector4 translationAndScale = new Vector4((float)translation.x, (float)translation.y, (float)scale.x, (float)scale.y);

        var primitiveComponent = GetComponentsInChildren<CesiumGltfPrimitiveComponent>();

        foreach (var pPrimitive in primitiveComponent)
        {
            var mr = pPrimitive.GetComponent<Renderer>();
            UnityEngine.Material pCesiumData = null;// mr.material;
            var pMaterial = mr.material;
            // If this material uses material layers and has the Cesium user data,
            // set the parameters on each material layer that maps to this overlay
            // tile.
            if (pCesiumData)
            {
                string name = rasterTile.pOverlay.name;

                // for (int i = 0; i < pCesiumData.LayerNames.Num(); ++i)
                // {
                //     if (pCesiumData.LayerNames[i] != name)
                //     {
                //         continue;
                //     }
                // CesiumTextureUtility.saveImage(pPrimitive.transform.GetSiblingIndex() + "ss.jpg", pTexture);
                pMaterial.SetTexture("_Texture", pTexture);
                pMaterial.SetVector("_TranslationScale", translationAndScale);
                pMaterial.SetFloat("_TextureCoordinateIndex", pPrimitive.overlayTextureCoordinateIDToUVIndex[textureCoordinateID]);
                // }
            }
            else
            {
                // CesiumTextureUtility.saveImage(pPrimitive.transform.GetSiblingIndex() + "ss.jpg", pTexture);
                pMaterial.SetTexture("_Texture", pTexture);
                pMaterial.SetVector("_TranslationScale", translationAndScale);
                pMaterial.SetFloat("__TextureCoordinateIndex", pPrimitive.overlayTextureCoordinateIDToUVIndex[textureCoordinateID]);
            }

        }
    }

    private static void loadModelGameThreadPart(CesiumGltfComponent gltf, LoadModelResult loadResult, double4x4 cesiumToUnrealTransform)
    {
        string meshName = loadResult.name + "";
        gltf.name = meshName;

        var child = new GameObject();
        child.name = meshName;
        var mf = child.AddComponent<MeshFilter>();
        var mr = child.AddComponent<MeshRenderer>();
        var pMesh = child.AddComponent<CesiumGltfPrimitiveComponent>();
        pMesh.tile_url = loadResult.pModel.extras["Cesium3DTiles_TileUrl"]?.ToString();
        Debug.Log("-------------------->" + pMesh.tile_url);

        pMesh.overlayTextureCoordinateIDToUVIndex = loadResult.overlayTextureCoordinateIDToUVIndex;
        pMesh.HighPrecisionNodeTransform = loadResult.transform;
        pMesh.UpdateTransformFromCesium(cesiumToUnrealTransform);

        mf.mesh = loadResult.RenderData;
        Gltf.Material material = loadResult.pMaterial ?? defaultMaterial;
        Gltf.MaterialPBRMetallicRoughness pbr = material.pbrMetallicRoughness ?? defaultPbrMetallicRoughness;
        var pBaseMaterial = (loadResult.onlyWater || !loadResult.onlyLand) ? gltf.BaseMaterialWithWater : gltf.BaseMaterial;
        var pMaterial = Instantiate<UnityEngine.Material>(pBaseMaterial);
        SetGltfParameterValues(loadResult, material, pbr, pMaterial);
        mr.material = pMaterial;
        child.transform.parent = gltf.transform;
    }

    private static void SetGltfParameterValues(LoadModelResult loadResult, Gltf.Material material, MaterialPBRMetallicRoughness pbr, UnityEngine.Material pMaterial)
    {
        foreach (var textureCoordinateSet in loadResult.textureCoordinateParameters)
        {
            pMaterial.SetFloat(textureCoordinateSet.Key, textureCoordinateSet.Value);
        }

        if (pbr.baseColorFactor.Count >= 3)
        {
            pMaterial.SetVector("_baseColorFactor", new Vector3((float)pbr.baseColorFactor[0], (float)pbr.baseColorFactor[1], (float)pbr.baseColorFactor[2]));
        }
        pMaterial.SetFloat("_metallicFactor", (float)pbr.metallicFactor);
        pMaterial.SetFloat("_roughnessFactor", (float)pbr.roughnessFactor);
        pMaterial.SetFloat("_opacityMask", 1.0f);

        applyTexture(
            pMaterial,
            "_baseColorTexture",
            loadResult.baseColorTexture);
        applyTexture(
            pMaterial,
            "_metallicRoughnessTexture",
            loadResult.metallicRoughnessTexture);
        applyTexture(
            pMaterial,
            "_normalTexture",
            loadResult.normalTexture);
        bool hasEmissiveTexture = applyTexture(
            pMaterial,
            "_emissiveTexture",
            loadResult.emissiveTexture);
        applyTexture(
            pMaterial,
            "_occlusionTexture",
            loadResult.occlusionTexture);

        if (material.emissiveFactor.Count >= 3)
        {
            pMaterial.SetVector(
                "_emissiveFactor",
                new Vector4(
                   (float)material.emissiveFactor[0],
                   (float)material.emissiveFactor[1],
                   (float)material.emissiveFactor[2]));
        }
        else if (hasEmissiveTexture)
        {
            // When we have an emissive texture but not a factor, we need to use a
            // factor of vec3(1.0). The default, vec3(0.0), would disable the emission
            // from the texture.
            pMaterial.SetVector(
                "_emissiveFactor",
                new Vector4(1.0f, 1.0f, 1.0f));
        }
    }

    private static bool applyTexture(UnityEngine.Material pMaterial, string info, Texture2D pLoadedTexture)
    {
        if (pLoadedTexture == null)
        {
            return false;
        }
        pMaterial.SetTexture(info, pLoadedTexture);
        return true;
    }

    private static void loadMesh(List<LoadModelResult> result, Model model, Gltf.Mesh mesh, double4x4 transform, CreateModelOptions options)
    {
        Debug.Log("loadMesh");

        foreach (MeshPrimitive primitive in mesh.primitives)
        {
            loadPrimitive(result, model, mesh, primitive, transform, options);
        }
    }

    private static void loadPrimitive(List<LoadModelResult> result, Model model, Gltf.Mesh mesh, MeshPrimitive primitive, double4x4 transform, CreateModelOptions options)
    {
        Debug.Log("loadPrimitive");
        if (!primitive.attributes.TryGetValue("POSITION", out var positionAccessorID))
        {
            // This primitive doesn't have a POSITION semantic, ignore it.
            return;
        }
        Accessor pPositionAccessor = Model.getSafe(model.accessors, positionAccessorID);
        if (!pPositionAccessor)
        {
            // Position accessor does not exist, so ignore this primitive.
            return;
        }

        AccessorView<float3> positionView = new AccessorView<float3>(model, pPositionAccessor);

        if (primitive.indices < 0 || primitive.indices >= model.accessors.Count)
        {
            var size = positionView.size();
            var data = new byte[positionView.size() * sizeof(int)];
            var syntheticIndexBuffer = new AccessorView<int>(data, sizeof(int), 0, size * sizeof(int));
            for (int i = 0; i < positionView.size(); ++i)
            {
                syntheticIndexBuffer[i] = i;
            }
            loadPrimitive(
                result,
                model,
                mesh,
                primitive,
                transform,
                options,
                pPositionAccessor,
                positionView,
                syntheticIndexBuffer);
        }
        else
        {
            loadIndexedPrimitive(
                result,
                model,
                mesh,
                primitive,
                transform,
                options,
                pPositionAccessor,
                positionView);
        }
    }

    private static void loadIndexedPrimitive(List<LoadModelResult> result, Model model, Gltf.Mesh mesh, MeshPrimitive primitive, double4x4 transform, CreateModelOptions options, Accessor positionAccessor, AccessorView<float3> positionView)
    {
        Accessor indexAccessorGltf = model.accessors[primitive.indices];
        if (indexAccessorGltf.componentType == Accessor.ComponentType.BYTE)
        {
            AccessorView<byte> indexAccessor = new AccessorView<byte>(model, primitive.indices);
            loadPrimitive(
                result,
                model,
                mesh,
                primitive,
                transform,
                options,
                positionAccessor,
                positionView,
                indexAccessor);
        }
        else if (
          indexAccessorGltf.componentType == Accessor.ComponentType.UNSIGNED_BYTE)
        {
            AccessorView<byte> indexAccessor = new AccessorView<byte>(model, primitive.indices);
            loadPrimitive(
                result,
                model,
                mesh,
                primitive,
                transform,
                options,
                positionAccessor,
                positionView,
                indexAccessor);
        }
        else if (
          indexAccessorGltf.componentType == Accessor.ComponentType.SHORT)
        {
            AccessorView<Int16> indexAccessor = new AccessorView<Int16>(model, primitive.indices);
            loadPrimitive(
                result,
                model,
                mesh,
                primitive,
                transform,
                options,
                positionAccessor,
                positionView,
                indexAccessor);
        }
        else if (
          indexAccessorGltf.componentType == Accessor.ComponentType.UNSIGNED_SHORT)
        {
            AccessorView<UInt16> indexAccessor = new AccessorView<ushort>(model, primitive.indices);
            loadPrimitive(
                result,
                model,
                mesh,
                primitive,
                transform,
                options,
                positionAccessor,
                positionView,
                indexAccessor);
        }
        else if (
          indexAccessorGltf.componentType == Accessor.ComponentType.UNSIGNED_INT)
        {
            AccessorView<uint> indexAccessor = new AccessorView<uint>(model, primitive.indices);
            loadPrimitive(
                result,
                model,
                mesh,
                primitive,
                transform,
                options,
                positionAccessor,
                positionView,
                indexAccessor);
        }
    }
    [StructLayout(LayoutKind.Sequential)]
    struct Vertex
    {
        public float3 position;
        public float3 normal;
        public float3 tangent;

        public float3 bitangent;

        public float2 UV0;
        public float2 UV1;
        public float2 UV2;
        public float2 UV3;


        public float2 this[int i]
        {
            get
            {
                switch (i)
                {
                    case 0: return UV0;
                    case 1: return UV1;
                    case 2: return UV2;
                    case 3: return UV3;
                }
                return UV0;
            }
            set
            {
                switch (i)
                {
                    case 0: UV0 = value; break;
                    case 1: UV1 = value; break;
                    case 2: UV2 = value; break;
                    case 3: UV3 = value; break;
                }
            }
        }
    }

    private static void loadPrimitive<T>(List<LoadModelResult> result, Model model, Gltf.Mesh mesh, MeshPrimitive primitive, double4x4 transform,
    CreateModelOptions options, Accessor positionAccessor,
    AccessorView<float3> positionView, AccessorView<T> indicesView) where T : unmanaged
    {

        Debug.Log("loadPrimitive<T>");

        if (primitive.mode != MeshPrimitive.Mode.TRIANGLES &&
            primitive.mode != MeshPrimitive.Mode.TRIANGLE_STRIP)
        {
            // TODO: add support for primitive types other than triangles.
            Debug.Log($"Primitive mode {primitive.mode} is not supported");
            return;
        }

        string name = "glTF";
        if (model.extras.TryGetValue("Cesium3DTiles_TileUrl", out var urlIt))
        {
            name = urlIt.HasValues ? urlIt.Value<string>() : "glTF";
            // name = constrainLength(name, 256);
        }

        var meshIt = model.meshes.FindIndex(x => x == mesh);
        if (meshIt >= 0)
        {
            int meshIndex = meshIt;//meshIt - model.meshes.First();
            name += $" mesh {meshIndex}";
        }

        var primitiveIndex = mesh.primitives.FindIndex(x => x == primitive);
        if (primitiveIndex >= 0)
        {
            name += $" primitive {primitiveIndex}";
        }

        LoadModelResult primitiveResult = new LoadModelResult(name);
        if (positionView.status() != AccessorViewStatus.Valid)
        {
            Debug.Log($"{name}: Invalid position buffer");
            return;
        }


        if (indicesView.status() != AccessorViewStatus.Valid)
        {
            Debug.Log($"{name}: Invalid indices buffer");
            return;
        }

        AccessorView<float3> normalAccessor = null;
        bool hasNormals = false;
        if (primitive.attributes.TryGetValue("NORMAL", out var normalAccessorID))
        {
            normalAccessor = new AccessorView<float3>(model, normalAccessorID);
            hasNormals = normalAccessor.status() == AccessorViewStatus.Valid;
            if (!hasNormals)
            {
                Debug.Log($"{name}: Invalid normal buffer. Flat normal will be auto-generated instead");
            }
        }

        int materialID = primitive.material;
        Gltf.Material material = materialID >= 0 && materialID < model.materials.Count
                 ? model.materials[materialID]
                 : defaultMaterial;
        MaterialPBRMetallicRoughness pbrMetallicRoughness = material.pbrMetallicRoughness ? material.pbrMetallicRoughness : defaultPbrMetallicRoughness;

        bool hasNormalMap = material.normalTexture;
        if (hasNormalMap)
        {
            Gltf.Texture pTexture = Model.getSafe(model.textures, material.normalTexture.index);
            hasNormalMap = pTexture != null && Model.getSafe(model.images, pTexture.source) != null;
        }

        bool needsTangents = hasNormalMap || options.alwaysIncludeTangents;

        bool hasTangents = false;

        AccessorView<float4> tangentAccessor = null;

        if (primitive.attributes.TryGetValue("TANGENT", out var tangentAccessorID))
        {
            tangentAccessor = new AccessorView<float4>(model, tangentAccessorID);
            hasTangents = tangentAccessor.status() == AccessorViewStatus.Valid;
            if (!hasTangents)
            {
                Debug.Log($"{name}: Invalid tangent buffer.");
            }
        }

        applyWaterMask(model, primitive, primitiveResult);

        // The water effect works by animating the normal, and the normal is
        // expressed in tangent space. So if we have water, we need tangents.
        if (primitiveResult.onlyWater || primitiveResult.waterMaskTexture)
        {
            needsTangents = true;
        }

        UnityEngine.Mesh RenderData = new UnityEngine.Mesh();
        {
            Debug.Log("compute AA bounding box");

            var min = positionAccessor.min;
            var max = positionAccessor.max;
            double3 minPosition = new double3(double.MaxValue);
            double3 maxPosition = new double3(double.MinValue);
            if (min.Count != 3 || max.Count != 3)
            {
                for (int i = 0; i < positionView.size(); ++i)
                {
                    minPosition.x = math.min(minPosition.x, positionView[i].x);
                    minPosition.y = math.min(minPosition.y, positionView[i].y);
                    minPosition.z = math.min(minPosition.z, positionView[i].z);

                    maxPosition.x = math.max(maxPosition.x, positionView[i].x);
                    maxPosition.y = math.max(maxPosition.y, positionView[i].y);
                    maxPosition.z = math.max(maxPosition.z, positionView[i].z);
                }
            }
            else
            {
                minPosition = new double3(min[0], min[1], min[2]);
                maxPosition = new double3(max[0], max[1], max[2]);
            }
            Bounds aaBox = new Bounds();
            aaBox.min = new Vector3((float)minPosition.x, (float)minPosition.y, (float)minPosition.z);
            aaBox.max = new Vector3((float)maxPosition.x, (float)maxPosition.y, (float)maxPosition.z);
            RenderData.bounds = aaBox;

            // aaBox.GetCenterAndExtents(
            //     RenderData.Bounds.Origin,
            //     RenderData.Bounds.BoxExtent);
            // RenderData.Bounds.SphereRadius = 0.0f;
        }

        int[] indices = null;
        if (primitive.mode == MeshPrimitive.Mode.TRIANGLES)
        {
            Debug.Log("copy TRIANGLE indices");
            indices = new int[(int)indicesView.size()];
            for (int i = 0; i < indicesView.size(); ++i)
            {
                indices[i] = (int)Convert.ChangeType(indicesView[i], TypeCode.Int32);
            }
        }
        else
        {
            // assume TRIANGLE_STRIP because all others are rejected earlier.
            Debug.Log("copy TRIANGLE_STRIP indices");
            indices = new int[3 * (indicesView.size() - 2)];
            for (int i = 0; i < indicesView.size() - 2; ++i)
            {
                if (i % 2 > 0)
                {
                    indices[3 * i] = (int)(object)indicesView[i];
                    indices[3 * i + 1] = (int)(object)indicesView[i + 2];
                    indices[3 * i + 2] = (int)(object)indicesView[i + 1];
                }
                else
                {
                    indices[3 * i] = (int)(object)indicesView[i];
                    indices[3 * i + 1] = (int)(object)indicesView[i + 1];
                    indices[3 * i + 2] = (int)(object)indicesView[i + 2];
                }
            }
        }

        // If we don't have normals, the gltf spec prescribes that the client
        // implementation must generate flat normals, which requires duplicating
        // vertices shared by multiple triangles. If we don't have tangents, but
        // need them, we need to use a tangent space generation algorithm which
        // requires duplicated vertices.
        bool duplicateVertices = !hasNormals || (needsTangents && !hasTangents);

        NativeArray<Vertex> StaticMeshBuildVertices = new NativeArray<Vertex>(duplicateVertices ? indices.Length : positionView.size(), Allocator.Temp);
        {
            if (duplicateVertices)
            {
                Debug.Log("copy duplicated positions");
                for (int i = 0; i < indices.Length; ++i)
                {
                    Vertex vertex = StaticMeshBuildVertices[i];
                    int vertexIndex = (int)indices[i];
                    vertex.position = positionView[vertexIndex];
                    vertex.UV0 = new float2(0.0f, 0.0f);
                    vertex.UV2 = new float2(0.0f, 0.0f);
                    StaticMeshBuildVertices[i] = vertex;
                    // RenderData.Bounds.SphereRadius = FMath::Max(
                    //     (vertex.Position - RenderData.Bounds.Origin).Size(),
                    //     RenderData.Bounds.SphereRadius);
                }
            }
            else
            {
                Debug.Log("copy positions");
                for (int i = 0; i < StaticMeshBuildVertices.Length; ++i)
                {
                    Vertex vertex = new Vertex();
                    vertex.position = (float3)positionView[i];
                    vertex.position.z = -vertex.position.z;
                    vertex.UV0 = new float2(0.0f, 0.0f);
                    vertex.UV2 = new float2(0.0f, 0.0f);

                    StaticMeshBuildVertices[i] = vertex;
                    // RenderData.Bounds.SphereRadius = FMath::Max((vertex.Position - RenderData.Bounds.Origin).Size(), RenderData.Bounds.SphereRadius);
                }
            }
        }
        if (primitive.attributes.TryGetValue("COLOR_0", out var colorAccessorID))
        {
            Debug.Log("copy colors");
            // hasVertexColors = createAccessorView(model, colorAccessorID, ColorVisitor{ duplicateVertices, StaticMeshBuildVertices, indices});
        }
        // LODResources.bHasColorVertexData = hasVertexColors;

        // We need to copy the texture coordinates associated with each texture (if
        // any) into the the appropriate UVs slot in FStaticMeshBuildVertex.

        Dictionary<int, int> textureCoordinateMap = new Dictionary<int, int>();
        {
            Debug.Log("loadTextures");
            primitiveResult.baseColorTexture = loadTexture(model, pbrMetallicRoughness.baseColorTexture);
            primitiveResult.metallicRoughnessTexture = loadTexture(model, pbrMetallicRoughness.metallicRoughnessTexture);
            primitiveResult.normalTexture = loadTexture(model, material.normalTexture);
            primitiveResult.occlusionTexture = loadTexture(model, material.occlusionTexture);
            primitiveResult.emissiveTexture = loadTexture(model, material.emissiveTexture);
        }

        {
            Debug.Log("updateTextureCoordinates");
            primitiveResult
                .textureCoordinateParameters = new Dictionary<string, int>();
            primitiveResult
                .textureCoordinateParameters["baseColorTextureCoordinateIndex"] =
                updateTextureCoordinates(
                    model,
                    primitive,
                    duplicateVertices,
                    StaticMeshBuildVertices,
                    indices,
                    pbrMetallicRoughness.baseColorTexture,
                    textureCoordinateMap);
            primitiveResult.textureCoordinateParameters
                ["metallicRoughnessTextureCoordinateIndex"] = updateTextureCoordinates(
                model,
                primitive,
                duplicateVertices,
                StaticMeshBuildVertices,
                indices,
                pbrMetallicRoughness.metallicRoughnessTexture,
                textureCoordinateMap);
            primitiveResult
                .textureCoordinateParameters["normalTextureCoordinateIndex"] =
                updateTextureCoordinates(
                    model,
                    primitive,
                    duplicateVertices,
                    StaticMeshBuildVertices,
                    indices,
                    material.normalTexture,
                    textureCoordinateMap);
            primitiveResult
                .textureCoordinateParameters["occlusionTextureCoordinateIndex"] =
                updateTextureCoordinates(
                    model,
                    primitive,
                    duplicateVertices,
                    StaticMeshBuildVertices,
                    indices,
                    material.occlusionTexture,
                    textureCoordinateMap);
            primitiveResult
                .textureCoordinateParameters["emissiveTextureCoordinateIndex"] =
                updateTextureCoordinates(
                    model,
                    primitive,
                    duplicateVertices,
                    StaticMeshBuildVertices,
                    indices,
                    material.emissiveTexture,
                    textureCoordinateMap);

            for (int i = 0; i < primitiveResult.overlayTextureCoordinateIDToUVIndex.Length; ++i)
            {
                string attributeName = "_CESIUMOVERLAY_" + i;
                if (primitive.attributes.ContainsKey(attributeName))
                {
                    primitiveResult.overlayTextureCoordinateIDToUVIndex[i] =
                        updateTextureCoordinates(
                            model,
                            primitive,
                            duplicateVertices,
                            StaticMeshBuildVertices,
                            indices,
                            attributeName,
                            textureCoordinateMap);
                }
                else
                {
                    primitiveResult.overlayTextureCoordinateIDToUVIndex[i] = 0;
                }
            }
        }

        // TangentX: Tangent
        // TangentY: Bi-tangent
        // TangentZ: Normal

        if (hasNormals)
        {
            if (duplicateVertices)
            {
                Debug.Log("copy normals for duplicated vertices");
                for (int i = 0; i < indices.Length; ++i)
                {
                    Vertex vertex = StaticMeshBuildVertices[i];
                    int vertexIndex = (int)indices[i];
                    vertex.normal = (float3)normalAccessor[vertexIndex];
                    StaticMeshBuildVertices[i] = vertex;
                }
            }
            else
            {
                Debug.Log("copy normals");
                for (int i = 0; i < StaticMeshBuildVertices.Length; ++i)
                {
                    Vertex vertex = StaticMeshBuildVertices[i];
                    vertex.normal = (float3)normalAccessor[i];
                    StaticMeshBuildVertices[i] = vertex;
                }
            }
        }
        else
        {
            Debug.Log("compute flat normals");
            computeFlatNormals(indices, StaticMeshBuildVertices);
        }

        if (hasTangents)
        {
            if (duplicateVertices)
            {
                Debug.Log("copy tangents for duplicated vertices");
                for (int i = 0; i < indices.Length; ++i)
                {
                    Vertex vertex = StaticMeshBuildVertices[i];
                    int vertexIndex = (int)indices[i];
                    float4 tangent = (float4)tangentAccessor[vertexIndex];
                    vertex.tangent = tangent.xyz;
                    vertex.bitangent = math.cross(vertex.normal, vertex.tangent) * tangent.w;
                    StaticMeshBuildVertices[i] = vertex;
                }
            }
            else
            {
                Debug.Log("copy tangents");
                for (int i = 0; i < StaticMeshBuildVertices.Length; ++i)
                {
                    Vertex vertex = StaticMeshBuildVertices[i];
                    float4 tangent = (float4)tangentAccessor[i];
                    vertex.tangent = tangent.xyz;
                    vertex.bitangent = math.cross(vertex.normal, vertex.tangent) * tangent.w;

                    StaticMeshBuildVertices[i] = vertex;
                }
            }
        }

        if (needsTangents && !hasTangents)
        {
            // Use mikktspace to calculate the tangents.
            // Note that this assumes normals and UVs are already populated.
            Debug.Log("compute tangents");
            computeTangentSpace(StaticMeshBuildVertices);
        }

        {
            Debug.Log("init buffers");
            var layout = new[]
      {
            new VertexAttributeDescriptor(VertexAttribute.Position, VertexAttributeFormat.Float32, 3),
            new VertexAttributeDescriptor(VertexAttribute.Normal, VertexAttributeFormat.Float32, 3),
            new VertexAttributeDescriptor(VertexAttribute.Tangent, VertexAttributeFormat.Float32, 3),
            new VertexAttributeDescriptor(VertexAttribute.Color, VertexAttributeFormat.Float32, 3),
            new VertexAttributeDescriptor(VertexAttribute.TexCoord0, VertexAttributeFormat.Float32, 2),
            new VertexAttributeDescriptor(VertexAttribute.TexCoord1, VertexAttributeFormat.Float32, 2),
            new VertexAttributeDescriptor(VertexAttribute.TexCoord2, VertexAttributeFormat.Float32, 2),
            new VertexAttributeDescriptor(VertexAttribute.TexCoord3, VertexAttributeFormat.Float32, 2)
        };

            RenderData.SetVertexBufferParams(StaticMeshBuildVertices.Length, layout);
            RenderData.SetVertexBufferData(StaticMeshBuildVertices, 0, 0, StaticMeshBuildVertices.Length);
        }

        // Note that we're reversing the order of the indices, because the change
        // from the glTF right-handed to the Unreal left-handed coordinate system
        // reverses the winding order.
        // Note also that we don't want to just flip the index buffer, since that
        // will change the order of the faces.
        if (duplicateVertices)
        {
            Debug.Log("reverse winding order of duplicated vertices");
            for (int i = 2; i < indices.Length; i += 3)
            {
                indices[i - 2] = (int)i;
                indices[i - 1] = (int)i - 1;
                indices[i] = (int)i - 2;
            }
        }
        else
        {
            Debug.Log("reverse winding order");
            for (int i = 2; i < indices.Length; i += 3)
            {
                Swap(indices, i - 2, i);
            }
        }

        {
            Debug.Log("SetIndices");
            // RenderData.SetIndexBufferParams()
            RenderData.SetIndices(indices, MeshTopology.Triangles, 0);
        }
        primitiveResult.pModel = model;
        primitiveResult.pMeshPrimitive = primitive;
        primitiveResult.RenderData = RenderData;
        primitiveResult.transform = transform;
        primitiveResult.pMaterial = material;
        // load primitive metadata
        primitiveResult.Metadata = loadMetadataPrimitive(model, primitive);
        result.Add(primitiveResult);
    }
    private static Texture2D loadTexture(Model model, TextureInfo gltfTexture)
    {
        if (gltfTexture == null || gltfTexture.index < 0 || gltfTexture.index >= model.textures.Count)
        {
            if (gltfTexture != null && gltfTexture.index >= 0)
            {
                Debug.Log($"Texture index must be less than {{model.textures.Count}}, but is {{gltfTexture.index}}");
            }
            return null;
        }
        Gltf.Texture texture = model.textures[gltfTexture.index];
        return TextureUtility.loadTextureAnyThreadPart(model, texture);
    }

    private static int updateTextureCoordinates(Model model, MeshPrimitive primitive, bool duplicateVertices, NativeArray<Vertex> vertices, int[] indices, TextureInfo texture, Dictionary<int, int> textureCoordinateMap)
    {
        if (texture == null) return 0;
        return updateTextureCoordinates(model,
        primitive,
        duplicateVertices,
        vertices,
        indices,
        "TEXCOORD_" + texture.texCoord,
        textureCoordinateMap);
    }

    private static int updateTextureCoordinates(Model model, MeshPrimitive primitive, bool duplicateVertices, NativeArray<Vertex> vertices, int[] indices, string attributeName, Dictionary<int, int> textureCoordinateMap)
    {
        if (!primitive.attributes.TryGetValue(attributeName, out var uvAccessorID))
        {
            // Texture not used, texture coordinates don't matter.
            return 0;
        }

        if (textureCoordinateMap.TryGetValue(uvAccessorID, out var coordinate))
        {
            // Texture coordinates for this accessor are already populated.
            return coordinate;
        }

        int textureCoordinateIndex = textureCoordinateMap.Count;
        textureCoordinateMap[uvAccessorID] = textureCoordinateIndex;

        AccessorView<float2> uvAccessor = new AccessorView<float2>(model, uvAccessorID);
        if (uvAccessor.status() != AccessorViewStatus.Valid)
        {
            return 0;
        }

        if (duplicateVertices)
        {
            for (int i = 0; i < indices.Length; ++i)
            {
                var vertex = vertices[i];
                int vertexIndex = indices[i];
                if (vertexIndex >= 0 && vertexIndex < uvAccessor.size())
                {
                    vertex[textureCoordinateIndex] = uvAccessor[vertexIndex];
                }
                else
                {
                    vertex[textureCoordinateIndex] = new float2(0.0f, 0.0f);
                }
                vertices[i] = vertex;
            }
        }
        else
        {
            for (int i = 0; i < vertices.Length; ++i)
            {
                var vertex = vertices[i];
                if (i >= 0 && i < uvAccessor.size())
                {
                    vertex[textureCoordinateIndex] = uvAccessor[i];
                }
                else
                {
                    vertex[textureCoordinateIndex] = new float2(0.0f, 0.0f);
                }
                vertices[i] = vertex;
            }
        }
        return textureCoordinateIndex;
    }

    private static void computeFlatNormals(int[] indices, NativeArray<Vertex> vertices)
    {
        // Compute flat normals
        for (int i = 0; i < indices.Length; i += 3)
        {
            Vertex v0 = vertices[i];
            Vertex v1 = vertices[i + 1];
            Vertex v2 = vertices[i + 2];

            float3 v01 = v1.position - v0.position;
            float3 v02 = v2.position - v0.position;
            float3 normal = math.cross(v01, v02);

            v0.normal = v1.normal = v2.normal = float3.zero;
            v0.bitangent = v1.bitangent = v2.bitangent = float3.zero;
            v0.normal = v1.normal = v2.normal = math.normalizesafe(normal);

            vertices[i] = v0;
        }
    }

    private static void computeTangentSpace(NativeArray<Vertex> vertices)
    {
        MikkGenerator.GenerateTangentSpace
        (33,
        //获取面数
        (face) => 33,
        //mikkGetPosition      
        (int FaceIdx, int VertIdx, out float x, out float y, out float z) =>
         {
             var position = vertices[FaceIdx * 3 + VertIdx].position;
             x = position.x;
             y = position.y;
             z = position.z;
         },
        //mikkGetNormal
        (int FaceIdx, int VertIdx, out float x, out float y, out float z) =>
         {
             var normal = vertices[FaceIdx * 3 + VertIdx].normal;
             x = normal.x;
             y = normal.y;
             z = normal.z;
         },
        //mikkGetTexCoord
        (int FaceIdx, int VertIdx, out float u, out float v) =>
        {
            var uv = vertices[FaceIdx * 3 + VertIdx].UV0;
            u = uv.x;
            v = uv.y;
        },
        //mikkSetTSpaceBasic
        (FaceIdx, VertIdx, x, y, z, sign) =>
        {
            Vertex vertex = vertices[FaceIdx * 3 + VertIdx];
            vertex.tangent = new float3(x, y, z);
            vertex.bitangent = sign * math.cross(vertex.normal, vertex.tangent);
        });
    }

    static void Swap<T>(T[] list, int indexA, int indexB) where T : struct
    {
        T tmp = list[indexA];
        list[indexA] = list[indexB];
        list[indexB] = tmp;
    }



    private static MetadataPrimitive loadMetadataPrimitive(Model model, MeshPrimitive primitive)
    {
        ModelEXT_feature_metadata metadata = model.getExtension<ModelEXT_feature_metadata>();
        if (!metadata)
        {
            return new MetadataPrimitive();
        }

        MeshPrimitiveEXT_feature_metadata primitiveMetadata = primitive.getExtension<MeshPrimitiveEXT_feature_metadata>();
        if (!primitiveMetadata)
        {
            return new MetadataPrimitive();
        }

        return new MetadataPrimitive(model, primitive, metadata, primitiveMetadata);
    }



    private static void applyWaterMask(Model model, MeshPrimitive primitive, LoadModelResult primitiveResult)
    {
        if (primitive.extras.TryGetValue("OnlyWater", out var onlyWaterIt) && (bool)onlyWaterIt
         && primitive.extras.TryGetValue("OnlyLand", out var onlyLandIt) && (bool)onlyLandIt)
        {
            Debug.Log("water mask");
            bool onlyWater = (bool)onlyWaterIt;
            bool onlyLand = (bool)onlyLandIt;
            primitiveResult.onlyWater = onlyWater;
            primitiveResult.onlyLand = onlyLand;
            if (!onlyWater && !onlyLand)
            {
                // We have to use the water mask
                var waterMaskTextureIdIt = primitive.extras["WaterMaskTex"];
                if (waterMaskTextureIdIt.HasValues)
                {
                    int waterMaskTextureId = (int)(waterMaskTextureIdIt);
                    TextureInfo waterMaskInfo = new TextureInfo();
                    waterMaskInfo.index = waterMaskTextureId;
                    if (waterMaskTextureId >= 0 && waterMaskTextureId < model.textures.Count)
                    {
                        primitiveResult.waterMaskTexture = loadTexture(model, waterMaskInfo);
                    }
                }
            }
        }
        else
        {
            primitiveResult.onlyWater = false;
            primitiveResult.onlyLand = true;
        }

        var waterMaskTranslationXIt = primitive.extras["WaterMaskTranslationX"];
        var waterMaskTranslationYIt = primitive.extras["WaterMaskTranslationY"];
        var waterMaskScaleIt = primitive.extras["WaterMaskScale"];

        if (waterMaskTranslationXIt.HasValues &&
        waterMaskTranslationXIt.Type == JTokenType.Float &&
            waterMaskTranslationYIt.HasValues &&
            waterMaskTranslationYIt.Type == JTokenType.Float &&
            waterMaskScaleIt.HasValues &&
            waterMaskScaleIt.Type == JTokenType.Float)
        {
            primitiveResult.waterMaskTranslationX = (double)waterMaskTranslationXIt;
            primitiveResult.waterMaskTranslationY = (double)waterMaskTranslationYIt;
            primitiveResult.waterMaskScale = (double)waterMaskScaleIt;
        }
    }

    private static void loadNode(List<LoadModelResult> result, Model model, Node node, double4x4 transform, CreateModelOptions options)
    {
        double4x4 nodeTransform = transform;

        double4x4 matrix = node.matrix;
        bool isIdentityMatrix = false;
        var boolMat = matrix == double4x4.identity;
        isIdentityMatrix = math.all(boolMat.c0) && math.all(boolMat.c1) && math.all(boolMat.c2) && math.all(boolMat.c3) && math.all(boolMat.c0);

        if (!isIdentityMatrix)
        {
            double4x4 nodeTransformGltf = matrix;
            nodeTransform = math.mul(nodeTransform, nodeTransformGltf);
        }
        else
        {
            double4x4 translation = double4x4.identity;
            translation[3] = new double4(node.translation[0], node.translation[1], node.translation[2], 1.0);
            quaternion rotationQuat = quaternion.identity;

            // if (node.rotation.size() == 4)
            // {
            //     rotationQuat[0] = node.rotation[0];
            //     rotationQuat[1] = node.rotation[1];
            //     rotationQuat[2] = node.rotation[2];
            //     rotationQuat[3] = node.rotation[3];
            // }

            double4x4 scale = double4x4.identity;
            scale[0].x = node.scale[0];
            scale[1].y = node.scale[1];
            scale[2].z = node.scale[2];
            // nodeTransform = nodeTransform * translation * glm::dmat4(rotationQuat) * scale;
            nodeTransform = math.mul(nodeTransform, math.mul(translation, scale));
        }

        int meshId = node.mesh;
        if (meshId >= 0 && meshId < model.meshes.Count)
        {
            Gltf.Mesh mesh = model.meshes[meshId];
            loadMesh(result, model, mesh, nodeTransform, options);
        }

        foreach (int childNodeId in node.children)
        {
            if (childNodeId >= 0 && childNodeId < model.nodes.Count)
            {
                loadNode(result, model, model.nodes[childNodeId], nodeTransform, options);
            }
        }
    }

    private static void applyGltfUpAxisTransform(Model model, ref double4x4 rootTransform)
    {
        if (!model.extras.TryGetValue("gltfUpAxis", out var gltfUpAxisIt))
        {
            // The default up-axis of glTF is the Y-axis, and no other
            // up-axis was specified. Transform the Y-axis to the Z-axis,
            // to match the 3D Tiles specification
            rootTransform = math.mul(rootTransform, AxisTransforms.Y_UP_TO_Z_UP);
            return;
        }
        int gltfUpAxisValue = ((int)gltfUpAxisIt);
        if (gltfUpAxisValue == (int)(Axis.X))
        {
            rootTransform = math.mul(rootTransform, AxisTransforms.X_UP_TO_Z_UP);
        }
        else if (gltfUpAxisValue == (int)(Axis.Y))
        {
            rootTransform = math.mul(rootTransform, AxisTransforms.Y_UP_TO_Z_UP);
        }
        else if (gltfUpAxisValue == (int)(Axis.Z))
        {
            // No transform required
        }
        else
        {
            Debug.Log(string.Format("Unknown gltfUpAxis value: {0}", gltfUpAxisValue));
        }
    }

    private static void applyRtcCenter(Model model, ref double4x4 rootTransform)
    {
        if (!model.extras.TryGetValue("RTC_CENTER", out var rtcCenterIt))
        {
            return;
        }
        if (rtcCenterIt.Type != JTokenType.Array)
        {
            return;
        }
        var pArray = rtcCenterIt as JArray;
        if (pArray.Count != 3)
        {
            Debug.Log(string.Format("The RTC_CENTER must have a size of 3, but has {0}", pArray.Count));
            return;
        }
        double x = ((double)pArray[0]);
        double y = ((double)pArray[1]);
        double z = ((double)pArray[2]);
        double4x4 rtcTransform = new double4x4(
      new double4(1.0, 0.0, 0.0, 0.0),
      new double4(0.0, 1.0, 0.0, 0.0),
      new double4(0.0, 0.0, 1.0, 0.0),
      new double4(x, y, z, 1.0));
        rootTransform = math.mul(rootTransform, rtcTransform);
    }
}